Skip to content

docs: add CONTEXT.md, the vocabulary and the house rules - #491

Merged
jdatcmd merged 2 commits into
mainfrom
docs/context-md
Aug 7, 2026
Merged

docs: add CONTEXT.md, the vocabulary and the house rules#491
jdatcmd merged 2 commits into
mainfrom
docs/context-md

Conversation

@jdatcmd

@jdatcmd jdatcmd commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Orientation for whoever works here next, human or agent. It owns the words: what the storage units are called, what the code naming conventions are, and what a test is expected to argue.

It does not compete with what exists. The table at the top says who owns what, because four documents with overlapping scope is how they start disagreeing:

document owns
design/NATIVE_FORMAT_AND_INTERFACE_SPEC.md what the format and interface ARE
HANDOFF.md what has happened, what is in flight
design/ROADMAP.md what is planned
CONTEXT.md the words

The section worth having

"Words that do not line up, and will mislead you" records naming drift that has actually cost time. Each item was verified, not remembered:

The test-discipline section

Written as rules, each with the incident behind it, since that is what makes a rule survive: assert the premise; prove the guard by removal; make the removal proof fail for the stated reason; never gate on luck (#487); pin known-wrong behaviour as an assertion rather than an echo; never pipe a captured string into a reader whose exit status is the answer (#486).

Verification

Every path, symbol and number was checked rather than written from memory: all ten referenced files exist, every named symbol resolves, the suite count (132) comes from --list-suites rather than a parse of the array, and there are no em or en dashes. docs_style.sh globs docs/*.md and README.md, so a root-level file is outside its scope; the content follows the same rules regardless.

🤖 Generated with Claude Code

Orientation for whoever works here next, human or agent. It owns the words:
what the storage units are called, what the code naming conventions are, and
what a test is expected to argue. The spec still owns what the format IS,
HANDOFF.md what has happened, and ROADMAP.md what is planned; the table at the
top says so, because four documents with overlapping scope is how they start
disagreeing.

The section worth having is "Words that do not line up", which records the
naming drift that has actually cost time, each item verified rather than
remembered:

- A stripe IS a row group. The spec says row group, the GUC and the per-table
  option say stripe, and both are in the tree.
- chunk_group_row_limit is a different setting and does not control the group
  counters, which is not guessable from its name.
- EXPLAIN's "Chunk Groups" counters count ROW GROUPS. Confirmed in
  columnar_reader.c, where groupsRead and groupsSkipped are incremented per
  NativeRowGroupMetadata as the scan walks rowGroupIndex, and measured: 200,000
  rows report 2 groups at the default stripe_row_limit whatever
  chunk_group_row_limit is set to, and 20 at stripe_row_limit => 10000. Anyone
  building a fixture with groups to prune needs that sentence.
- "Pushed-Down Filters" and "Usable Skip Predicates" are different numbers, and
  the difference is what #477 hid and #479 exposed.

The test-discipline section is the distinguishing one and is deliberately
written as rules with the incident behind each: assert the premise, prove the
guard by removal, make the removal proof fail for the STATED reason, never gate
on luck (#487), pin known-wrong behaviour as an assertion rather than an echo,
and never pipe a captured string into a reader whose exit status is the answer
(#486).

Every path, symbol and number in the file was checked rather than written from
memory: the suite count comes from the runner, not from a parse of the array.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@ChronicallyJD ChronicallyJD left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes on one line, and it is the line most likely to be trusted.

First, disclosure: I was writing a CONTEXT.md when this appeared, and I independently reached two of the same conclusions — that a stripe is a row group, and that EXPLAIN's Chunk Groups counters count row groups, measured the same way (200,000 rows reporting 2 at the default and 20 at stripe_row_limit => 10000). Yours is more thorough than mine and I am not going to open a competing one. Mine is discarded except for the two additions at the bottom.

Vector is not a fixed 1024 values on the shipped path

- **Vector**: a fixed run of 1024 values inside a column chunk. The unit of
  decode, of data skipping, and of vectorized execution.

A vector holds up to pgcolumnar.chunk_group_row_limit rows — default 10000, PGC_USERSET, range 100 to INT_MAX, and per-table overridable. Measured on the same 200,000-row shape, rebuilding the table at each setting and running the identical predicate:

chunk_group_row_limit Columnar Vectors Skipped
10000 4
5000 8
1024 39

Each matches the arithmetic for the row group that gets read: 50,000 rows at 10,000 is 5 vectors with 4 below the predicate; at 5,000 it is 10 with 8 below; at 1,024 it is ~49 with 39 below. The 1024 run also grows a Rows Removed by Filter: 64 line, because 190,000 is not a multiple of 1024 so the straddling vector is decoded and filtered — which is itself confirmation the boundary moved.

Where 1024 is real: COLUMNAR_NATIVE_VECTOR_LENGTH in columnar.h:47, written into the native storage row as vectorLength by columnar_write_state.c:529. That is the native (PGCN v1) format's fixed geometry, which is what spec section 4 describes — along with a 122880-row group limit that also is not what ships (stripe_row_limit defaults to 150000). There is no pgcolumnar.vector_length GUC in the tree.

So the sentence is true of the format the spec specifies and false of the build people are running, which is precisely the failure mode this document exists to stop. It is also in tension with your own later bullet: if a vector were fixed at 1024, chunk_group_row_limit would size nothing, and the reason it "does not control the group counters" is that it controls the vector counters instead.

Suggested:

Vector: a run of up to pgcolumnar.chunk_group_row_limit rows (default 10000) inside a column chunk. The unit of encoding, of data skipping within a row group, and of vectorized execution. The native format fixes this at 1024; the classic path does not, and Columnar Vectors Skipped moves with the setting.

This matters more than a definition usually would: someone building a fixture to exercise vector-level skipping would size it in 1024-row units, get one vector per group at the default, and conclude the skipping does not work.

"Section 2 of the spec is authoritative" is the root of it. The spec describes the native format; a good deal of it is not the shipped default. Worth a sentence saying which document wins when they disagree, since your ownership table gives the spec "what the format and interface ARE" and this is a case where that reading misleads.

Two things mine had that yours does not

1. projection is overloaded, in the same way chunk group is. You define Projection as the secondary physical ordering, which is right. But pgcolumnar.enable_column_projection uses the word for something entirely different — reading only the columns a query references — and it appears in EXPLAIN as Columnar Projected Columns. Two unrelated concepts, one word, both user-facing. It belongs in "words that do not line up": qualify the second as column projection and never use the bare word for it.

2. Pruning and filtering are different outcomes and the plan prints both. Chunk Groups Removed by Filter is work never done; Rows Removed by Filter is work done and thrown away. They can appear on the same node — the 1024 row above produced exactly that. Since the document already separates "pushed down" from "usable", separating "pruned" from "filtered" completes the same thought, and it is the distinction that tells you whether a predicate helped.

Otherwise

The ownership table at the top is the part I would keep hardest. Four documents with overlapping scope is exactly how HANDOFF.md and the design docs started disagreeing, and naming the owner per question is cheaper than reconciling them later.

The test-discipline section reads as rules with incidents attached, which is the form that survives. I would add one from today, if you want it: a green suite proves nothing until it has failed for the stated reason — the removal proof you already describe, but run against the unfixed code rather than against a re-armed defect. #488 ran its new checks against stock main and got 3 failures out of 46, all behavioural, with every premise still passing in both arms. That signature — premises green in both, behaviour red in one — is what distinguishes a test that discriminates from one that merely passes.

Two corrections, both the same mistake: asserting local facts as repository
facts.

HANDOFF.md is NOT in the repository. It is excluded per clone through
.git/info/exclude and has never been committed, so a fresh clone has no such
file, and a table listing it beside the spec and the roadmap sends a new reader
looking for something that is not there. It is now described as what it is, a
local continuity record worth reading if you have one and worth depending on
never.

The build section described this machine: no PostgreSQL on the host, a
container, a read-only source mount. None of that is true of the project. It now
describes the actual interface, which is ordinary PGXS and a suite that stands up
its own cluster, and says that a containerised environment is a property of the
machine and belongs in local notes.

The verification that missed this used `[ -f ]`, which proves a file is on disk.
The question was whether it is in the repository, which is `git ls-files`. Every
path in the file has now been checked that way; HANDOFF.md was the only one that
failed, and it failed silently because it does exist here.

CHANGELOG.md takes the freed row in the ownership table.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jdatcmd

jdatcmd commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator Author

Correcting the PR body above, which repeated a mistake now fixed in the file.

HANDOFF.md is not in this repository. It is excluded per clone through .git/info/exclude and has never been committed, so a fresh clone has no such file. The ownership table in my description lists it beside the spec and the roadmap, which would send a new reader looking for something that is not there. The table in the file now reads:

document owns
design/NATIVE_FORMAT_AND_INTERFACE_SPEC.md what the format and the interface ARE
design/ROADMAP.md what is planned
CHANGELOG.md what changed, per release

with HANDOFF.md described separately as what it actually is: a local continuity record, worth reading if you have one and worth depending on never.

The build section described this machine rather than the project (no PostgreSQL on the host, a container, a read-only source mount). None of that is true of the repository. It now describes the real interface, which is ordinary PGXS plus a suite that stands up its own cluster, and says that a containerised environment is a property of the machine and belongs in local notes.

How the verification missed it is the part worth keeping. I checked every referenced path with [ -f ], which proves a file is on disk. The question for a committed document is whether it is in the repository, which is git ls-files. HANDOFF.md passed the first test precisely because it does exist here, and failed the second. Every path in the file has now been checked the second way, and it was the only one that failed.

That is the same shape as the defects this file's own test-discipline section is about: a check that passes for a reason unrelated to the property being claimed.

@jdatcmd
jdatcmd merged commit 99261db into main Aug 7, 2026
11 checks passed
@jdatcmd
jdatcmd deleted the docs/context-md branch August 7, 2026 19:26
jdatcmd pushed a commit that referenced this pull request Aug 7, 2026
…follow-up)

CONTEXT.md defines a vector as "a fixed run of 1024 values inside a column
chunk". That is the native (PGCN v1) geometry from spec section 4. It is not
what the classic path does, and CONTEXT.md is the document people will trust.

A vector holds up to pgcolumnar.chunk_group_row_limit rows: default 10000,
PGC_USERSET, range 100 to INT_MAX, per-table overridable. Rebuilding the same
200,000 rows at three settings and running the same predicate:

    chunk_group_row_limit    Columnar Vectors Skipped
                    10000                           4
                     5000                           8
                     1024                          39

Each matches the arithmetic for the row group that gets read. The 1024 run also
grows a "Rows Removed by Filter: 64" line, because 190000 is not a multiple of
1024, so the straddling vector is decoded and filtered rather than skipped --
independent confirmation that the boundary moved.

Where 1024 is real: COLUMNAR_NATIVE_VECTOR_LENGTH in columnar.h:47, written into
the native storage row as vectorLength by columnar_write_state.c:529. Spec
section 4 also gives a 122880-row group limit, which is likewise not what ships
(stripe_row_limit defaults to 150000). There is no pgcolumnar.vector_length GUC.

This also removes a contradiction inside the file: the misleading-words section
says chunk_group_row_limit "does not control the group counters", which is true,
but a vector fixed at 1024 would leave that setting sizing nothing. It sizes the
vector, so it moves Vectors Skipped instead. That table is now in the file.

Two further entries in the same section, from the same review:

- projection names two unrelated things -- the secondary physical ordering, and
  enable_column_projection / "Columnar Projected Columns" for reading fewer
  columns. Same collision shape as chunk group.
- pruning and filtering are different outcomes and a plan prints both. Chunk
  Groups Removed by Filter is work never done; Rows Removed by Filter is work
  done and discarded.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QRQYekvivA4RLDnndhanHK
jdatcmd added a commit that referenced this pull request Aug 7, 2026
A vector is chunk_group_row_limit rows, not a fixed 1024 (#491 follow-up)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants